﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Forms;
using Fasterflect;
using O2DESNet;
using ShkSimulation.core.template;
using ShkSimulation.core.template.agency;
using ShkSimulation.desktop.util;
using ShkSimulation.carsharing.impl.elements;

namespace ShkSimulation.desktop.component.tools {
    public sealed partial class InstancePropertyView : UserControl {
        
        public object ShowingObject { get; set; }

        public InstancePropertyView() {
            InitializeComponent();
            this.Dock = DockStyle.Fill;
            PropertyTreeView.TreeViewNodeSorter = Comparer<TreeNode>.Create((a, b) => String.Compare(a.Text, b.Text, StringComparison.Ordinal));
            this.Load += (_, _) => { SetInstance(ShowingObject); };
        }
        
        private void PropertyTreeView_KeyDown(object sender, KeyEventArgs e) {
            if (e.Control && e.KeyCode == Keys.C) {
                Clipboard.SetText(PropertyTreeView.SelectedNode.Text);
            }
        }

        public void SetInstance(object obj) {
            this.ShowingObject = obj;
            PropertyTreeView.Nodes.Clear();
            if (obj == null) {
                return;
            }
            TreeNode root;
            if (obj is Agency agency) {
                root = RecurBuildAgency(agency, Exclude);
            } else if (obj is Network network) {
                root = BuildNetwork(network, Exclude);
            } else  {
                root = BuildWithoutRecur("root", obj, Exclude);
            }
            List<TreeNode> nodes = new List<TreeNode>();
            foreach (TreeNode rootNode in root.Nodes) {
                nodes.Add(rootNode);
            }
            PropertyTreeView.Nodes.AddRange(nodes.ToArray());
        }

        private TreeNode BuildNetwork(Network network, ISet<string> exclude) {
            IList<PropertyInfo> propertyInfos = network.GetType().Properties(Flags.InstancePublic);
            TreeNode root = new TreeNode("路网[" + network.UniqueId + "]");
            List<TreeNode> nodes = new List<TreeNode>();
            foreach (var propertyInfo in propertyInfos) {
                if (exclude.Contains(propertyInfo.Name)) {
                    continue;
                }
                if (propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string)) {
                    TreeNode treeNode = new TreeNode($"{propertyInfo.Name}: {propertyInfo.GetValue(network, null)}");
                    nodes.Add(treeNode);
                } else if (propertyInfo.PropertyType == typeof(KeyValuePair<,>) || propertyInfo.PropertyType == typeof(KeyValuePair)) {
                    TreeNode treeNode = new TreeNode($"{propertyInfo.Name}: {propertyInfo.GetValue(network, null)}");
                    nodes.Add(treeNode);
                }  else if (typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType)) {
                    IEnumerable enumerable = propertyInfo.GetValue(network) as IEnumerable;
                    if (enumerable != null) {
                        nodes.Add(BuildEnumerable(GetEnumerableName(propertyInfo), enumerable, exclude));
                    }
                }
            }
            root.Nodes.AddRange(nodes.ToArray());
            return root;
        }

        private static HashSet<string> Exclude = new HashSet<string>() {
            "LogFile", "DebugMode", "Pointer", "Order", "Attributes", "HourCounters"
        };

        private TreeNode RecurBuildAgency(Agency agency, ISet<string> exclude) {
            List<PropertyInfo> propertyInfos = new List<PropertyInfo>(agency.GetType().Properties(Flags.InstanceAnyVisibility));
            TreeNode root = new TreeNode(agency.Name);
            List<TreeNode> nodes = new List<TreeNode>();
            foreach (var propertyInfo in propertyInfos) {
                if (exclude.Contains(propertyInfo.Name)) {
                    continue;
                }
                if (propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string)) {
                    TreeNode treeNode = new TreeNode($"{propertyInfo.Name}: {propertyInfo.GetValue(agency, null)}");
                    nodes.Add(treeNode);
                } else if (propertyInfo.PropertyType == typeof(HourCounter)) {
                    TreeNode treeNode = BuildWithoutRecur(propertyInfo.Name, propertyInfo.GetValue(agency, null), exclude);
                    nodes.Add(treeNode);
                } else if (propertyInfo.PropertyType == typeof(Road) || propertyInfo.PropertyType == typeof(Station)) {
                    Agency cur = propertyInfo.GetValue(agency) as Agency;
                    if (cur == null) {
                        continue;
                    }
                    TreeNode treeNode = BuildWithoutRecur(cur.Name, cur, exclude);
                    nodes.Add(treeNode);
                } else if (typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType)) {
                    IEnumerable enumerable = propertyInfo.GetValue(agency) as IEnumerable;
                    if (enumerable != null) {
                        nodes.Add(BuildEnumerable(GetEnumerableName(propertyInfo), enumerable, exclude));
                    }
                }
            }
            root.Nodes.AddRange(nodes.ToArray());
            return root;
        }
        
        private TreeNode BuildEnumerable(string name, IEnumerable enumerable, ISet<string> exclude) {
            TreeNode treeNode = new TreeNode(name);
            List<TreeNode> temp = new List<TreeNode>();
            if (enumerable is IDictionary dictionary) {
                var fitst = dictionary.Keys.First();
                if (fitst is int) {
                    enumerable = dictionary.Values;
                } else {
                    enumerable = dictionary.Keys;
                }
            }
            foreach (object obj in enumerable) {
                Type type = obj.GetType();
                if (type.IsPrimitive || type.IsValueType || type == typeof(string)) {
                    TreeNode node = new TreeNode($"{type.Name}: {obj}");
                    temp.Add(node);
                } else {
                    temp.Add(BuildWithoutRecur(obj.GetType().Name, obj, exclude));
                }
            }
            treeNode.Nodes.AddRange(temp.ToArray());
            return treeNode;
        }

        private TreeNode BuildWithoutRecur(string name, object obj, ISet<string> exclude) {
            IList<PropertyInfo> propertyInfos = obj.GetType().Properties(Flags.InstancePublic);
            TreeNode root = new TreeNode(name);
            List<TreeNode> nodes = new List<TreeNode>();
            foreach (var propertyInfo in propertyInfos) {
                if (exclude.Contains(propertyInfo.Name)) {
                    continue;
                }
                if (propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string)) {
                    TreeNode treeNode = new TreeNode($"{propertyInfo.Name}: {propertyInfo.GetValue(obj, null)}");
                    nodes.Add(treeNode);
                } else if (propertyInfo.PropertyType == typeof(HourCounter)) {
                    TreeNode treeNode = BuildWithoutRecur(propertyInfo.Name, propertyInfo.GetValue(obj) as HourCounter, exclude);
                    nodes.Add(treeNode);
                } else if (typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType)) {
                    IEnumerable enumerable = propertyInfo.GetValue(obj) as IEnumerable;
                    if (enumerable != null) {
                        TreeNode treeNode = new TreeNode($"{propertyInfo.Name}: {enumerable.Count()}");
                        nodes.Add(treeNode);
                    }
                }
            }
            root.Nodes.AddRange(nodes.ToArray());
            return root;
        }
        
        private string GetEnumerableName(PropertyInfo propertyInfo) {
            string rootName = propertyInfo.PropertyType.Name;
            if (rootName.Contains("`")) {
                rootName = rootName.Split("`")[0];
            }
            return $"{propertyInfo.Name}[{rootName}]";
        }

    }
}
